import numpy as np


class SimpleNeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size, learning_rate=0.1):
        """
        初始化神经网络

        参数:
            input_size: 输入层神经元数量
            hidden_size: 隐藏层神经元数量
            output_size: 输出层神经元数量
            learning_rate: 学习率
        """
        # 初始化权重和偏置 - 使用随机值初始化
        # 输入层到隐藏层的权重矩阵
        self.weights_input_hidden = np.random.randn(
            input_size, hidden_size) * 0.01
        # 隐藏层的偏置
        self.bias_hidden = np.zeros((1, hidden_size))
        # 隐藏层到输出层的权重矩阵
        self.weights_hidden_output = np.random.randn(
            hidden_size, output_size) * 0.01
        # 输出层的偏置
        self.bias_output = np.zeros((1, output_size))
        # 学习率
        self.learning_rate = learning_rate

    def sigmoid(self, x):
        """
        sigmoid激活函数

        参数:
            x: 输入值

        返回:
            sigmoid(x)
        """
        return 1.0 / (1.0 + np.exp(-x))

    def sigmoid_derivative(self, x):
        """
        sigmoid函数的导数

        参数:
            x: sigmoid函数的输出值

        返回:
            sigmoid函数在x处的导数
        """
        return x * (1.0 - x)

    def forward(self, X):
        """
        前向传播

        参数:
            X: 输入数据，shape为(样本数, 特征数)

        返回:
            输出层的输出
        """
        # 计算隐藏层的净输入
        self.hidden_input = np.dot(
            X, self.weights_input_hidden) + self.bias_hidden
        # 应用激活函数得到隐藏层的输出
        self.hidden_output = self.sigmoid(self.hidden_input)

        # 计算输出层的净输入
        self.output_input = np.dot(
            self.hidden_output, self.weights_hidden_output) + self.bias_output
        # 应用激活函数得到输出层的输出
        self.output = self.sigmoid(self.output_input)

        return self.output

    def backward(self, X, y, output):
        """
        反向传播

        参数:
            X: 输入数据
            y: 目标值
            output: 前向传播的输出

        返回:
            无，但会更新网络的权重和偏置
        """
        # 计算输出层的误差
        output_error = y - output
        # 计算输出层的delta (误差 * sigmoid导数)
        output_delta = output_error * self.sigmoid_derivative(output)
        # 计算隐藏层的误差 (输出层delta通过权重反向传播)
        hidden_error = np.dot(output_delta, self.weights_hidden_output.T)
        # 计算隐藏层的delta
        hidden_delta = hidden_error * \
            self.sigmoid_derivative(self.hidden_output)
        # 更新权重和偏置
        # 隐藏层到输出层
        self.weights_hidden_output += self.learning_rate * \
            np.dot(self.hidden_output.T, output_delta)
        self.bias_output += self.learning_rate * \
            np.sum(output_delta, axis=0, keepdims=True)

        # 输入层到隐藏层
        self.weights_input_hidden += self.learning_rate * \
            np.dot(X.T, hidden_delta)
        self.bias_hidden += self.learning_rate * \
            np.sum(hidden_delta, axis=0, keepdims=True)

    def train(self, X, y, epochs):
        """
        训练神经网络

        参数:
            X: 训练数据
            y: 目标值
            epochs: 训练轮数
        """
        for epoch in range(epochs):
            # 前向传播
            output = self.forward(X)

            # 计算损失 (均方误差)
            loss = np.mean(np.square(y - output))

            # 反向传播
            self.backward(X, y, output)

            # 每100轮打印一次损失
            if epoch % 100 == 0:
                print(f"Epoch {epoch}, Loss: {loss}")

    def predict(self, X):
        """
        使用训练好的网络进行预测

        参数:
            X: 输入数据

        返回:
            预测结果
        """
        return self.forward(X)